package com.kaifamiao.servlet;

import com.kaifamiao.annotation.Controller;
import com.kaifamiao.annotation.RequestMapping;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lucien-ma
 * @date 2021/10/10
 * @apiNote
 */
public class DispatcherServlet extends HttpServlet {

    private ServletConfig config;
    private Map<String, String> handlerMapping = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        String packageName = config.getInitParameter("package");
        // 获取要扫描包的绝对路径
        String absolutePath = getAbsolutePath(packageName);
        // 获取该路径下的所有 .class文件
        String[] javaFiles = getJavaFiles(absolutePath);
        for (String file : javaFiles) {
            // 获取类名
            String className = file.replace(".class", "");
            try{
                // 根据全限定类名创建对象
                Class clazz = Class.forName(packageName + "." + className);
                // 判断类上是否有 @Controller 注解
                Annotation annotation = clazz.getAnnotation(Controller.class);
                if (annotation != null) {
                    // 有 @Controller 就进一步判断哪些方法上有 @RequestMapping 注解
                    Method[] methods = clazz.getDeclaredMethods();
                    for (Method method : methods) {
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        if (requestMapping != null) {
                            // 有@RequestMapping 注解的方法就存入到 map 集合中
                            handlerMapping.put(requestMapping.value(), clazz.getName() + "." + method.getName());
                        }
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 获取指定路径下的所有 .java文件名称
     * @param absolutePath
     * @return
     */
    private String[] getJavaFiles(String absolutePath) {
        File absoluteFile = new File(absolutePath);
        String[] list = absoluteFile.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".class");
            }
        });
        return list;
    }

    /**
     * 获取包的绝对路劲
     * @param packageName 包名
     * @return
     */
    private String getAbsolutePath(String packageName) {
        // 将 . 替换为系统对应的文件分层符
        String absolutePath = packageName.replace(".", File.separator);
        ServletContext servletContext = config.getServletContext();
        String realPath = servletContext.getRealPath("/");
        return realPath + "\\WEB-INF\\classes\\" + absolutePath;
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求的路径
        String uri = req.getRequestURI();
        String classAndMethod = handlerMapping.get(uri);
        if (classAndMethod != null) {
            int lastD = classAndMethod.lastIndexOf(".");
            String className = classAndMethod.substring(0, lastD);
            String methodName = classAndMethod.substring(lastD + 1);
            try {
                // 创建 Class 对象
                Class clazz = Class.forName(className);
                // 创建对应的对象
                Object object = clazz.getConstructor(null).newInstance(null);
                // 获取方法对象
                Method method = clazz.getDeclaredMethod(methodName);
                // 执行并获取返回值
                Object returnValue = method.invoke(object);
                // 给返回值加个标签，(为了看的清楚)
                resp.setContentType("text/html; charset=utf-8");
                returnValue = "<h2>"+returnValue+"</h2>";
                // 获取输出对象
                PrintWriter writer = resp.getWriter();
                // 将返回值输出到页面
                writer.write(returnValue.toString());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}
